/*==========================================================================
 *
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: tunnel.c
 *
 ***************************************************************************/

#include <math.h>
#include <malloc.h>
#include <d3d.h>
#include "d3ddemo.h"

/*
 * External funtions which perform much of the math.
 */
extern LPD3DVECTOR D3DVECTORNormalise(LPD3DVECTOR v);
extern LPD3DVECTOR D3DVECTORCrossProduct(LPD3DVECTOR lpd, LPD3DVECTOR lpa,
                                         LPD3DVECTOR lpb);
extern LPD3DMATRIX D3DMATRIXInvert(LPD3DMATRIX d, LPD3DMATRIX a);
extern LPD3DMATRIX D3DMATRIXSetRotation(LPD3DMATRIX lpM, LPD3DVECTOR lpD,
                                        LPD3DVECTOR lpU);
extern void spline(LPD3DVECTOR p, float t, LPD3DVECTOR p1, LPD3DVECTOR p2,
                   LPD3DVECTOR p3, LPD3DVECTOR p4);

/*
 * Globals to keep track of execute buffer
 */
static D3DEXECUTEDATA d3dExData;
static LPDIRECT3DEXECUTEBUFFER lpD3DExBuf;
static D3DEXECUTEBUFFERDESC debDesc;

/*
 * More globals
 */
LPDIRECT3DMATERIAL lpbmat;
LPDIRECT3DMATERIAL lpmat;   /* Material object */

/*
 * Global projection, view, world and identity matricies
 */
D3DMATRIXHANDLE hProj;
D3DMATRIX proj = {
    D3DVAL(2.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(2.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(1.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(-1.0), D3DVAL(0.0)
};
D3DMATRIXHANDLE hView;
D3DMATRIX view = {
    D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0), D3DVAL(1.0)
};
D3DMATRIXHANDLE hWorld;
D3DMATRIX world = {
    D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0)
};
D3DMATRIX identity = {
    D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0),
    D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0)
};

#define PI 3.14159265359

/*
 * These defines describe the section of the tube in the execute buffer at
 * one time. (Note, tube and tunnel are used interchangeably).
 */
#define SEGMENTS 20   /* Number of segments in memory at one time.  Each
                       * segment is made up oftriangles spanning between
                       * two rings.
                       */ 
#define SIDES 8       /* Number of sides on each ring. */
#define TEX_RINGS 5   /* Number of rings to stretch the texture over. */
#define NUM_V (SIDES*(SEGMENTS+1)) // Number of vertices in memory at once
#define NUM_TRI (SIDES*SEGMENTS*2) // Number of triangles in memory
#define TUBE_R 1.0       /* Radius of the tube. */
#define SPLINE_POINTS 50 /* Number of spline points to initially
                          * calculate.  The section in memory represents
                          * only a fraction of this.
                          */
/*
 * Movement and track scalars given in terms of position along the spline
 * curve.
 */
#define SEGMENT_LENGTH 0.05 /* Length of each segment along curve. */
#define SPEED 0.02          /* Amount to increment camera position along
                             * curve for each frame.
                             */
#define DEPTH 0.8           /* How close the camera can get to the end of
                             * track before new segments are added.
                             */
#define PATH_LENGTH (SPLINE_POINTS - 1) /*Total length of the tunnel.*/

/*
 * A global structure holding the tube data.
 */
static struct {
    LPD3DVERTEX lpV;        /* Points to the vertices. */
    LPD3DTRIANGLE lpTri;    /* Points to the triangles which make up the
                             * segments.
                             */
    int TriOffset;          /* Offset into the execute buffer were the
                             * triangle list is found.
                             */
    LPD3DVECTOR lpPoints;   /* Points to the points defining the spline
                             * curve.
                             */
    D3DMATERIALHANDLE hMat; /* Handle for the material on the tube. */
    D3DTEXTUREHANDLE hTex;  /* Handle for the texture on the material.*/
    D3DLIGHT light;             /* Structure defining the light. */
    LPDIRECT3DLIGHT lpD3DLight; /* Object pointer for the light. */
    D3DVECTOR cameraP, cameraD, cameraN; /* Vectors defining the camera 
                                          * position, direction and up.
                                          */
    float cameraPos;                     /* Camera position along the 
                                          * spline curve.
                                          */
    D3DVECTOR endP, endD, endN; /* Vectors defining the position, 
                                 * direction and up at the foremost end of
                                 * the section in memory.
                                 */
    float endPos; /* Position along the spline curve of the end. */
    int currentRing, currentSegment; /* Numbers of the ring and tube at 
                                      * the back end of the section.
                                      */
} tube;


/*
 * Creates a matrix which is equivalent to having the camera at a
 * specified position. This matrix can be used to convert vertices to
 * camera coordinates. lpP    Position of the camera. lpD    Direction of
 * view. lpN    Up vector. lpM    Matrix to update.
 */
void 
PositionCamera(LPD3DVECTOR lpP, LPD3DVECTOR lpD, LPD3DVECTOR lpN, 
               LPD3DMATRIX lpM)
{
    D3DMATRIX tmp;

    /*
     * Set the rotation part of the matrix and invert it. Vertices must be
     * inverse rotated to achieve the same result of a corresponding 
     * camera rotation.
     */
    tmp._14 = tmp._24 = tmp._34 = tmp._41 = tmp._42 = tmp._43 = (float)0.0;
    tmp._44 = (float)1.0;
    D3DMATRIXSetRotation(&tmp, lpD, lpN);
    D3DMATRIXInvert(lpM, &tmp);
    /*
     * Multiply the rotation matrix by a translation transform.  The
     * translation matrix must be applied first (left of rotation).
     */
    lpM->_41=-(lpM->_11 * lpP->x + lpM->_21 * lpP->y + lpM->_31 * lpP->z);
    lpM->_42=-(lpM->_12 * lpP->x + lpM->_22 * lpP->y + lpM->_32 * lpP->z);
    lpM->_43=-(lpM->_13 * lpP->x + lpM->_23 * lpP->y + lpM->_33 * lpP->z);
}

/*
 * Updates the given position, direction and normal vectors to a given
 * position on the spline curve.  The given up vector is used to determine
 * the new up vector.
 */
void 
MoveToPosition(float position, LPD3DVECTOR lpP, LPD3DVECTOR lpD, 
               LPD3DVECTOR lpN)
{
    LPD3DVECTOR lpSplinePoint[4];
    D3DVECTOR pp, x;
    int i, j;
    float t;

    /*
     * Find the four points along the curve which are around the position.
     */
    i = 0;
    t = position;
    while (t > 1.0) {
        i++;
        if (i == SPLINE_POINTS)
            i = 0;
        t -= (float)1.0;
    }
    for (j = 0; j < 4; j++) {
        lpSplinePoint[j] = &tube.lpPoints[i];
        i++;
        if (i == SPLINE_POINTS)
            i = 0;
    }
    /*
     * Get the point at the given position and one just before it.
     */
    spline(lpP, t, lpSplinePoint[0], lpSplinePoint[1], lpSplinePoint[2],
           lpSplinePoint[3]);
    spline(&pp, t - (float)0.01, lpSplinePoint[0], lpSplinePoint[1],
           lpSplinePoint[2], lpSplinePoint[3]);
    /*
     * Calculate the direction.
     */
    lpD->x = lpP->x - pp.x;
    lpD->y = lpP->y - pp.y;
    lpD->z = lpP->z - pp.z;
    D3DVECTORNormalise(lpD);
    /*
     * Find the new normal.  This method will work provided the change in
     * the normal is not very large.
     */
    D3DVECTORNormalise(lpN);
    D3DVECTORCrossProduct(&x, lpN, lpD);
    D3DVECTORCrossProduct(lpN, &x, lpD);
    lpN->x = -lpN->x;
    lpN->y = -lpN->y;
    lpN->z = -lpN->z;
    D3DVECTORNormalise(lpN);
}


/*
 * Generates a ring of vertices in a plane defined by n and the cross
 * product of n and p.  On exit, joint contains the vertices.  Join must
 * be pre-allocated. Normals are generated pointing in.  Texture
 * coordinates are generated along tu axis and are given along tv.
 */
static void 
MakeRing(LPD3DVECTOR p, LPD3DVECTOR d, LPD3DVECTOR n, float tv,
         LPD3DVERTEX joint)
{
    int spoke;
    float theta, u, v, x, y, z;
    D3DVECTOR nxd;

    D3DVECTORCrossProduct(&nxd, n, d);
    for (spoke = 0; spoke < SIDES; spoke++) {
        theta = (float)(2.0 * PI) * spoke / SIDES;
        /*
         * v, u defines a unit vector in the plane define by vectors nxd
         * and n.
         */
        v = (float)sin(theta);
        u = (float)cos(theta);
        /*
         * x, y, z define a unit vector in standard coordiante space
         */
        x = u * nxd.x + v * n->x;
        y = u * nxd.y + v * n->y;
        z = u * nxd.z + v * n->z;
        /*
         * Position, normals and texture coordiantes.
         */
        joint[spoke].x = (float)TUBE_R * x + p->x;
        joint[spoke].y = (float)TUBE_R * y + p->y;
        joint[spoke].z = (float)TUBE_R * z + p->z;
        joint[spoke].nx = -x;
        joint[spoke].ny = -y;
        joint[spoke].nz = -z;
        joint[spoke].tu = (float)1.0 - theta / (float)(2.0 * PI);
        joint[spoke].tv = tv;

    }
}


/*
 * Defines the triangles which form a segment between ring1 and ring2 and
 * stores them at lpTri.  lpTri must be pre-allocated.
 */
void 
MakeSegment(int ring1, int ring2, LPD3DTRIANGLE lpTri)
{
    int side, triangle = 0;

    for (side = 0; side < SIDES; side++) {
        /*
         * Each side consists of two triangles.
         */
        lpTri[triangle].v1 = ring1 * SIDES + side;
        lpTri[triangle].v2 = ring2 * SIDES + side;
        lpTri[triangle].v3 = ring2 * SIDES + ((side + 1) % SIDES);
        
        /*
         * for wireframe only need first two edges.
         * Start a two triangle flat fan for each tunnel face.
         */
                
        lpTri[triangle].wFlags = D3DTRIFLAG_STARTFLAT(1);
        lpTri[triangle].wFlags |= D3DTRIFLAG_EDGEENABLE1 |
                                  D3DTRIFLAG_EDGEENABLE2;
        
        triangle++;
        lpTri[triangle].v2 = ring2 * SIDES + ((side + 1) % SIDES);
        lpTri[triangle].v3 = ring1 * SIDES + ((side + 1) % SIDES);
        lpTri[triangle].v1 = ring1 * SIDES + side;
        
        /*
         * Dont need any edges for wireframe.
         */
        lpTri[triangle].wFlags = D3DTRIFLAG_EVEN;
        
        triangle++;
    }
}


/*
 * Creates a new segment of the tunnel at the current end position.
 * Creates a new ring and segment.
 */
void 
UpdateTubeInMemory(void)
{
    static int texRing = 0; /* Static counter defining the position of
                             * this ring on the texture.
                             */
    int endRing; /* The ring at the end of the tube in memory. */
    int RingOffset, SegmentOffset; /* Offsets into the vertex and triangle 
                                    * lists for the new data.
                                    */
    /*
     * Replace the back ring with a new ring at the front of the tube
     * in memory.
     */
    memcpy(&tube.lpV[SIDES], &tube.lpV[0], sizeof(tube.lpV[0]) * (NUM_V - SIDES));
    MakeRing(&tube.endP, &tube.endD, &tube.endN, texRing/(float)TEX_RINGS,
             &tube.lpV[0]);
    /*
     * Replace the back segment with a new segment at the front of the
     * tube in memory. Update the current end position of the tube in
     * memory.
     */
    endRing = (tube.currentRing + SEGMENTS) % (SEGMENTS + 1);
    MoveToPosition(tube.endPos, &tube.endP, &tube.endD, &tube.endN);
    /*
     * Update the execute buffer with the new vertices and triangles.
     */
    RingOffset = sizeof(D3DVERTEX) * tube.currentRing * SIDES;
    SegmentOffset = sizeof(D3DTRIANGLE) * tube.currentSegment * SIDES * 2;
    memset(&debDesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
    debDesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
    if (lpD3DExBuf->lpVtbl->Lock(lpD3DExBuf, &debDesc) != D3D_OK)
        return;
    memcpy((char *) debDesc.lpData,
           &tube.lpV[0], sizeof(D3DVERTEX) * NUM_V);
    lpD3DExBuf->lpVtbl->Unlock(lpD3DExBuf);
    /*
     * Update the position of the back of the tube in memory and texture
     * counter.
     */
    tube.currentRing = (tube.currentRing + 1) % (SEGMENTS + 1);
    tube.currentSegment = (tube.currentSegment + 1) % SEGMENTS;
    texRing = (texRing + 1) % TEX_RINGS;
}


/*
 * Move the camera through the tunnel.  Create new segments of the tunnel
 * when the camera gets close to the end of the section in memory.
 */
void 
MoveCamera(LPDIRECT3DDEVICE lpDev, LPDIRECT3DVIEWPORT lpView)
{
    /*
     * Update the position on curve and camera vectors.
     */
    tube.cameraPos += (float)SPEED;
    if (tube.cameraPos > PATH_LENGTH)
        tube.cameraPos -= PATH_LENGTH;
    MoveToPosition(tube.cameraPos, &tube.cameraP, &tube.cameraD,
                   &tube.cameraN);
    /*
     * If the camera is close to the end, add a new segment.
     */
    if (tube.endPos - tube.cameraPos < DEPTH) {
        tube.endPos = tube.endPos + (float)SEGMENT_LENGTH;
        if (tube.endPos > PATH_LENGTH)
            tube.endPos -= PATH_LENGTH;
        UpdateTubeInMemory();
    }
}


/*
 * Modify the buffer between rendering frames
 */
static void 
TickScene(LPDIRECT3DDEVICE lpDev, LPDIRECT3DVIEWPORT lpView)
{
    MoveCamera(lpDev, lpView);
}


/*
 * Each frame, renders the scene and calls TickScene to modify the object
 * for the next frame.
 */
BOOL
RenderScene(LPDIRECT3DDEVICE lpDev, LPDIRECT3DVIEWPORT lpView,
            LPD3DRECT lpExtent)
{
    HRESULT ddrval;

    /*
     * Move the camera by updating the view matrix and move the light.
     */
    PositionCamera(&tube.cameraP, &tube.cameraD, &tube.cameraN, &view);
    ddrval = lpDev->lpVtbl->SetMatrix(lpDev, hView, &view);
    if (ddrval != D3D_OK)
        return FALSE;

    tube.light.dvPosition.x = tube.cameraP.x;
    tube.light.dvPosition.y = tube.cameraP.y;
    tube.light.dvPosition.z = tube.cameraP.z;
    ddrval = tube.lpD3DLight->lpVtbl->SetLight(tube.lpD3DLight, &tube.light);
    if (ddrval != D3D_OK)
        return FALSE;
    /*
     * Execute the instruction buffer and update the view
     */
    ddrval = lpDev->lpVtbl->BeginScene(lpDev);
    if (ddrval != D3D_OK)
        return FALSE;
    ddrval = lpDev->lpVtbl->Execute(lpDev, lpD3DExBuf, lpView, D3DEXECUTE_CLIPPED);
    if (ddrval != D3D_OK)
        return FALSE;
    ddrval = lpDev->lpVtbl->EndScene(lpDev);
    if (ddrval != D3D_OK)
        return FALSE;
    ddrval = lpD3DExBuf->lpVtbl->GetExecuteData(lpD3DExBuf, &d3dExData);
    if (ddrval != D3D_OK)
        return FALSE;
    *lpExtent = d3dExData.dsStatus.drExtent;
    /*
     * Modify for the next time around
     */
    TickScene(lpDev, lpView);
    return TRUE;
}

void
OverrideDefaults(Defaults* defaults)
{
    defaults->rs.bZBufferOn = FALSE;
    defaults->rs.bPerspCorrect = TRUE;
    defaults->bClearsOn = FALSE;
    lstrcpy(defaults->Name, "Tunnel D3D Example");
}

BOOL
InitScene(void)
{
    float position;             /* Curve position counter. */
    int i;                      /* counter */

    /*
     * Reserved memory for vertices, triangles and spline points.
     */
    tube.lpV = (LPD3DVERTEX) malloc(sizeof(D3DVERTEX) * NUM_V);
    tube.lpTri = (LPD3DTRIANGLE) malloc(sizeof(D3DTRIANGLE) * NUM_TRI);
    tube.lpPoints = (LPD3DVECTOR) malloc(sizeof(D3DVECTOR)*SPLINE_POINTS);
    /*
     * Generate spline points
     */
    for (i = 0; i < SPLINE_POINTS; i++) {
#if 1
        tube.lpPoints[i].x = (float)(cos(i * 4.0) * 20.0);
        tube.lpPoints[i].y = (float)(sin(i * 4.0) * 20.0);
        tube.lpPoints[i].z = i * (float)20.0;
#else
        tube.lpPoints[i].x = (float)0.0;
        tube.lpPoints[i].y = (float)0.0;
        tube.lpPoints[i].z = i * (float)20.0;
#endif
    }
    /*
     * Create the initial tube section in memory.
     */
    tube.endN.x = (float)0.0;
    tube.endN.y = (float)1.0;
    tube.endN.z = (float)0.0;
    position = (float)0.0;
    for (i = 0; i < SEGMENTS + 1; i++) {
        MoveToPosition(position, &tube.endP, &tube.endD, &tube.endN);
        position += (float)SEGMENT_LENGTH;
        MakeRing(&tube.endP, &tube.endD, &tube.endN, 
                 (float)(i % TEX_RINGS) / TEX_RINGS,
                 &tube.lpV[(SEGMENTS - i) * SIDES]);
    }
    for (i = 0; i < SEGMENTS; i++)
        MakeSegment(i + 1, i, &tube.lpTri[i * SIDES * 2]);
    /*
     * Move the camera to the begining and set some globals
     */
    tube.cameraN.x = (float)0.0;
    tube.cameraN.y = (float)1.0;
    tube.cameraN.z = (float)0.0;
    MoveToPosition((float)0.0, &tube.cameraP, &tube.cameraD, &tube.cameraN);
    tube.currentRing = 0;
    tube.currentSegment = 0;
    tube.cameraPos = (float)0.0;
    tube.endPos = position;
    return TRUE;
}

void
ReleaseScene(void)
{
    if (tube.lpPoints)
        free(tube.lpPoints);
    if (tube.lpTri)
        free(tube.lpTri);
    if (tube.lpV)
        free(tube.lpV);
}

void
ReleaseView(LPDIRECT3DVIEWPORT lpView)
{
    if (lpView)
        lpView->lpVtbl->DeleteLight(lpView, tube.lpD3DLight);
    RELEASE(lpD3DExBuf);
    RELEASE(tube.lpD3DLight);
    RELEASE(lpmat);
    RELEASE(lpbmat);
}


/*
 * Builds the scene and initializes the execute buffer for rendering.
 * Returns 0 on failure.
 */
BOOL
InitView(LPDIRECTDRAW lpDD, LPDIRECT3D lpD3D, LPDIRECT3DDEVICE lpDev, 
           LPDIRECT3DVIEWPORT lpView, int NumTextures, 
           LPD3DTEXTUREHANDLE TextureHandle)
{
    /* Variables for exectue buffer generation */
    LPVOID lpBufStart, lpInsStart, lpPointer;
    LPDIRECT3DEXECUTEBUFFER lpD3DExCmdBuf;
    DWORD size;

    /* Background material variables */
    D3DMATERIAL bmat;
    D3DMATERIALHANDLE hbmat;
    D3DMATERIAL mat;

    /*
     * Set background to black material
     */
    if (lpD3D->lpVtbl->CreateMaterial(lpD3D, &lpbmat, NULL) != D3D_OK)
        return FALSE;
    memset(&bmat, 0, sizeof(D3DMATERIAL));
    bmat.dwSize = sizeof(D3DMATERIAL);
    bmat.dwRampSize = 1;
    lpbmat->lpVtbl->SetMaterial(lpbmat, &bmat);
    lpbmat->lpVtbl->GetHandle(lpbmat, lpDev, &hbmat);
    lpView->lpVtbl->SetBackground(lpView, hbmat);
    /*
     * Set the view, projection and world matricies in an execute buffer
     */
    MAKE_MATRIX(lpDev, hView, view);
    MAKE_MATRIX(lpDev, hProj, proj);
    MAKE_MATRIX(lpDev, hWorld, world);
    /*
     * Create an execute buffer
     */
    size = 0;
    size += sizeof(D3DINSTRUCTION) * 3;
    size += sizeof(D3DSTATE) * 4;
    memset(&debDesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
    debDesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
    debDesc.dwFlags = D3DDEB_BUFSIZE;
    debDesc.dwBufferSize = size;
    if (lpDev->lpVtbl->CreateExecuteBuffer(lpDev, &debDesc, &lpD3DExCmdBuf,
                                           NULL) != D3D_OK)
                                               return FALSE;
    if (lpD3DExCmdBuf->lpVtbl->Lock(lpD3DExCmdBuf, &debDesc) != D3D_OK)
        return FALSE;
    lpBufStart = debDesc.lpData;
    memset(lpBufStart, 0, size);
    lpPointer = lpBufStart;
    /*
     * Fill the execute buffer with instructions
     */
    lpInsStart = lpPointer;
    OP_STATE_TRANSFORM(3, lpPointer);
        STATE_DATA(D3DTRANSFORMSTATE_WORLD, hWorld, lpPointer);
        STATE_DATA(D3DTRANSFORMSTATE_VIEW, hView, lpPointer);
        STATE_DATA(D3DTRANSFORMSTATE_PROJECTION, hProj, lpPointer);
    OP_STATE_LIGHT(1, lpPointer);
        STATE_DATA(D3DLIGHTSTATE_AMBIENT, RGBA_MAKE(40, 40, 40, 40), lpPointer);
    OP_EXIT(lpPointer);
    /*
     * Setup the execute data describing the buffer
     */
    lpD3DExCmdBuf->lpVtbl->Unlock(lpD3DExCmdBuf);
    memset(&d3dExData, 0, sizeof(D3DEXECUTEDATA));
    d3dExData.dwSize = sizeof(D3DEXECUTEDATA);
    d3dExData.dwInstructionOffset = (ULONG) 0;
    d3dExData.dwInstructionLength = (ULONG) ((char *)lpPointer - (char*)lpInsStart);
    lpD3DExCmdBuf->lpVtbl->SetExecuteData(lpD3DExCmdBuf, &d3dExData);
    lpDev->lpVtbl->BeginScene(lpDev);
    lpDev->lpVtbl->Execute(lpDev, lpD3DExCmdBuf, lpView, D3DEXECUTE_UNCLIPPED);
    lpDev->lpVtbl->EndScene(lpDev);
    /*
     * We are done with the command buffer.
     */
    lpD3DExCmdBuf->lpVtbl->Release(lpD3DExCmdBuf);
    /*
     * Setup materials and lights
     */
    tube.hTex = TextureHandle[1];
    if (lpD3D->lpVtbl->CreateMaterial(lpD3D, &lpmat, NULL) != D3D_OK)
        return FALSE;
    memset(&mat, 0, sizeof(D3DMATERIAL));
    mat.dwSize = sizeof(D3DMATERIAL);
    mat.diffuse.r = (D3DVALUE)1.0;
    mat.diffuse.g = (D3DVALUE)1.0;
    mat.diffuse.b = (D3DVALUE)1.0;
    mat.diffuse.a = (D3DVALUE)1.0;
    mat.ambient.r = (D3DVALUE)1.0;
    mat.ambient.g = (D3DVALUE)1.0;
    mat.ambient.b = (D3DVALUE)1.0;
    mat.specular.r = (D3DVALUE)1.0;
    mat.specular.g = (D3DVALUE)1.0;
    mat.specular.b = (D3DVALUE)1.0;
    mat.power = (float)20.0;
    mat.dwRampSize = 16;
    mat.hTexture = tube.hTex;
    lpmat->lpVtbl->SetMaterial(lpmat, &mat);
    lpmat->lpVtbl->GetHandle(lpmat, lpDev, &tube.hMat);
    memset(&tube.light, 0, sizeof(D3DLIGHT));
    tube.light.dwSize = sizeof(D3DLIGHT);
    tube.light.dltType = D3DLIGHT_POINT;
    tube.light.dvPosition.x = tube.cameraP.x;
    tube.light.dvPosition.y = tube.cameraP.y;
    tube.light.dvPosition.z = tube.cameraP.z;
    tube.light.dcvColor.r = D3DVAL(0.9);
    tube.light.dcvColor.g = D3DVAL(0.9);
    tube.light.dcvColor.b = D3DVAL(0.9);
    tube.light.dvAttenuation0 = (float)0.0;
    tube.light.dvAttenuation1 = (float)0.0;
    tube.light.dvAttenuation2 = (float)0.05;
    if (lpD3D->lpVtbl->CreateLight(lpD3D, &tube.lpD3DLight, NULL)!=D3D_OK)
        return FALSE;
    if (tube.lpD3DLight->lpVtbl->SetLight(tube.lpD3DLight, &tube.light)
        !=D3D_OK)
        return FALSE;
    if (lpView->lpVtbl->AddLight(lpView, tube.lpD3DLight) != D3D_OK)
        return FALSE;

    /*
     * Create an execute buffer
     */
    size = sizeof(D3DVERTEX) * NUM_V;
    size += sizeof(D3DPROCESSVERTICES);
    size += sizeof(D3DINSTRUCTION) * 6;
    size += sizeof(D3DSTATE) * 4;
    size += sizeof(D3DTRIANGLE) * NUM_TRI;
    memset(&debDesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
    debDesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
    debDesc.dwFlags = D3DDEB_BUFSIZE;
    debDesc.dwBufferSize = size;
    if (lpDev->lpVtbl->CreateExecuteBuffer(lpDev, &debDesc, &lpD3DExBuf,
                                           NULL) != D3D_OK)
        return FALSE;
    /*
     * lock it so it can be filled
     */
    if (lpD3DExBuf->lpVtbl->Lock(lpD3DExBuf, &debDesc) != D3D_OK)
        return FALSE;
    lpBufStart = debDesc.lpData;
    memset(lpBufStart, 0, size);
    lpPointer = lpBufStart;
    VERTEX_DATA(tube.lpV, NUM_V, lpPointer);
    /*
     * Save the location of the first instruction and add instructions to
     * execute buffer.
     */
    lpInsStart = lpPointer;
    OP_STATE_LIGHT(1, lpPointer);
        STATE_DATA(D3DLIGHTSTATE_MATERIAL, tube.hMat, lpPointer);
    OP_PROCESS_VERTICES(1, lpPointer);
        PROCESSVERTICES_DATA(D3DPROCESSVERTICES_TRANSFORMLIGHT, 0, NUM_V, lpPointer);
    OP_STATE_RENDER(3, lpPointer);
        STATE_DATA(D3DRENDERSTATE_TEXTUREHANDLE, tube.hTex, lpPointer);
        STATE_DATA(D3DRENDERSTATE_WRAPU, TRUE, lpPointer);
        STATE_DATA(D3DRENDERSTATE_WRAPV, TRUE, lpPointer);
    /*
     * Make sure that the triangle data (not OP) will be QWORD aligned
     */
    if (QWORD_ALIGNED(lpPointer)) {
        OP_NOP(lpPointer);
    }
    OP_TRIANGLE_LIST(NUM_TRI, lpPointer);
        tube.TriOffset = (char *)lpPointer - (char *)lpBufStart;
        TRIANGLE_LIST_DATA(tube.lpTri, NUM_TRI, lpPointer);
    OP_EXIT(lpPointer);
    /*
     * Setup the execute data describing the buffer
     */
    lpD3DExBuf->lpVtbl->Unlock(lpD3DExBuf);
    memset(&d3dExData, 0, sizeof(D3DEXECUTEDATA));
    d3dExData.dwSize = sizeof(D3DEXECUTEDATA);
    d3dExData.dwVertexCount = NUM_V;
    d3dExData.dwInstructionOffset = (ULONG) ((char *)lpInsStart - (char *)lpBufStart);
    d3dExData.dwInstructionLength = (ULONG) ((char *)lpPointer - (char *)lpInsStart);
    lpD3DExBuf->lpVtbl->SetExecuteData(lpD3DExBuf, &d3dExData);

    return TRUE;
}
